#######主要计算calllog的基本，时间差，最大联系天数，联系密度等特征。
#######这一小模块中又分为四个小模块
#######主要通过列表和字典等数据结构来实现。
######主要分为两部分，第一部分将需要的东西存到self.save_data中，第二部分根据self.save_data计算特征
import datetime
import os
import re
from copy import deepcopy

def get_root_dir(path):
    path_list=path.split(os.path.sep)
    index=path_list.index("featurelib")
    return os.path.sep.join(path_list[:index+1])
    
class CallUnBase0V1Module5678:

    def __init__(self):
        self.save_data = {}
        self.raw_data = {}
        ###定位根目录
        cur_path = os.path.abspath(".")
        root_path = get_root_dir(cur_path)

        ###生成初始特征字典，置为"None"
        path = os.path.join(
            root_path,
            "feature_conf",
            "call",
            "un",
            "call_un_base0_v1",
            "call_un_base0_v1_module5678.featlist",
        )
        self.res_map = {
            k.strip().split("|")[0].strip(): "None"
            for k in open(path, encoding="utf-8")
        }

        self.function_map = {
            "cnt": self.cal_feature_sub_model_1,
            "cont": self.cal_feature_sub_model_2,
            "se": self.cal_feature_sub_model_3,
            "day": self.cal_feature_sub_model_4,
        }

    def main(self, data):
        self.gen_tags(data)
        res_map = self.cal_feature()
        return res_map

    ###打标签
    def gen_tags(self, data):
        for call in data["call_list"]:
            call["name_flag"] = "1" if len(call["name"].strip()) > 0 else "0"
            call["duration_flag"] = "1" if call["duration"] > 0 else "0"
            call["type_flag"] = call["type"] if call["type"] in ["1", "2"] else "0"
            call["call_date"] = call["calltime"].date()
        self.raw_data = deepcopy(data)

    ###初始化self.save_data
    def init_save_data(self):
        self.init_save_data_sub_model_1()
        self.init_save_data_sub_model_2()
        self.init_save_data_sub_model_3()
        self.init_save_data_sub_model_4()

    ###计算self.save_data
    def cal_save_data(self):
        self.cal_save_data_sub_model_1()
        self.cal_save_data_sub_model_2()
        self.cal_save_data_sub_model_3()
        self.cal_save_data_sub_model_4()

    ###计算特征
    def cal_feature(self):
        self.init_save_data()
        self.cal_save_data()
        res_map = {}
        for prefix, function in self.function_map.items():
            res_map_ = function()
            for feature_name, feature_value in res_map_.items():
                res_map[prefix + "_" + feature_name] = feature_value
        return res_map

    ###该模块主要为一些基本的cnt，ratio特征
    def init_save_data_sub_model_1(self):
        col1_used_label = {"duration": ["duration_1", "duration_all"]}
        col2_used_label = {"name": ["name_1", "name_all"], "type": ["type_1", "type_2"]}
        for col1 in col1_used_label:
            for col2 in col2_used_label:
                col1_label = col1_used_label[col1]
                col2_label = col2_used_label[col2]
                for label1 in col1_label:
                    for label2 in col2_label:
                        self.save_data[f"{label1}_{label2}_cnt"] = 0
                        self.save_data[f"{label1}_{label2}_phone_set"] = set()
        self.save_data["duration_1_duration_list"] = []
        self.save_data["duration_1_phone_type_dic"] = dict()

    def cal_save_data_sub_model_1(self):
        col1_list = ["duration"]
        col2_list = ["name", "type"]
        for call in self.raw_data["call_list"]:
            phone = call["phone"]
            if call["duration_flag"] == "1":
                self.save_data["duration_1_duration_list"].append(call["duration"])
                if phone not in self.save_data["duration_1_phone_type_dic"]:
                    self.save_data["duration_1_phone_type_dic"][phone] = set()
                self.save_data["duration_1_phone_type_dic"][phone].add(
                    call["type_flag"]
                )
            for col1 in col1_list:
                for col2 in col2_list:
                    for col1_value in [call[col1 + "_flag"], "all"]:
                        for col2_value in [call[col2 + "_flag"], "all"]:
                            name = (
                                col1 + "_" + col1_value + "_" + col2 + "_" + col2_value
                            )
                            name_cnt = name + "_cnt"
                            name_phone_set = name + "_phone_set"
                            if name_cnt not in self.save_data:
                                continue
                            self.save_data[name_cnt] += 1
                            self.save_data[name_phone_set].add(phone)

    def cal_feature_sub_model_1(self):
        res_map = {}
        col1_used_label = {"duration": ["duration_all", "duration_1"]}
        col2_used_label = {"name": ["name_all", "name_1"], "type": ["type_1", "type_2"]}
        all_call_cnt = self.save_data["duration_all_name_all_cnt"]
        all_phone_cnt = len(self.save_data["duration_all_name_all_phone_set"])
        for col1 in col1_used_label:
            for col2 in col2_used_label:
                col1_label = col1_used_label[col1]
                col2_label = col2_used_label[col2]
                for label1 in col1_label:
                    for label2 in col2_label:
                        res_map[f"{label1}_{label2}_call_cnt"] = self.save_data[
                            f"{label1}_{label2}_cnt"
                        ]
                        res_map[f"{label1}_{label2}_call_ratio"] = (
                            self.two_num_division(
                                res_map[f"{label1}_{label2}_call_cnt"], all_call_cnt
                            )
                        )
                        res_map[f"{label1}_{label2}_phone_cnt"] = len(
                            self.save_data[f"{label1}_{label2}_phone_set"]
                        )
                        res_map[f"{label1}_{label2}_phone_ratio"] = (
                            self.two_num_division(
                                res_map[f"{label1}_{label2}_phone_cnt"], all_phone_cnt
                            )
                        )
        res_map["duration_1_duratiuon_avg"] = self.two_num_division(
            sum(self.save_data["duration_1_duration_list"]),
            len(self.save_data["duration_1_duration_list"]),
        )
        good_phone_set = set()
        for k, v in self.save_data["duration_1_phone_type_dic"].items():
            if "1" in v and "2" in v:
                good_phone_set.add(k)
        res_map["duration_1_type_1_type_2_phone_cnt"] = len(good_phone_set)
        res_map["duration_1_type_1_type_2_phone_ratio"] = self.two_num_division(
            res_map["duration_1_type_1_type_2_phone_cnt"], all_phone_cnt
        )
        return res_map

    ###该模块主要为最大连续通话的一些特征
    def init_save_data_sub_model_2(self):
        self.save_data["max_continus"] = [0, set(), 0, set()]

    def cal_save_data_sub_model_2(self):
        date_set = set()
        for call in self.raw_data["call_list"]:
            date_set.add(call["call_date"])
        self.save_data["have_record_days_cnt"] = len(date_set)
        min_date, max_date = min(date_set), max(date_set)
        self.save_data["min_date"], self.save_data["max_date"] = min_date, max_date
        max_min_diff_day = (max_date - min_date).days
        date_list = [
            min_date + datetime.timedelta(days=i) for i in range(max_min_diff_day + 1)
        ]
        day_list = [1 if i in date_set else 0 for i in date_list]
        have_record_max_continus_day, have_record_max_continus_end_index = (
            self.get_max_continus(day_list, 1)
        )
        nohave_record_max_continus_day, nohave_record_max_continus_end_index = (
            self.get_max_continus(day_list, 0)
        )

        have_record_max_continus_min_day = date_list[
            have_record_max_continus_end_index
        ] - datetime.timedelta(days=have_record_max_continus_day - 1)
        have_record_max_continus_max_day = date_list[have_record_max_continus_end_index]
        self.save_data["have_record_max_continus"] = [
            have_record_max_continus_min_day,
            have_record_max_continus_max_day,
            have_record_max_continus_day,
        ]

        nohave_record_max_continus_max_day = (
            date_list[nohave_record_max_continus_end_index]
            if nohave_record_max_continus_end_index
            else self.save_data["min_date"] - datetime.timedelta(days=1)
        )
        nohave_record_max_continus_min_day = (
            date_list[nohave_record_max_continus_end_index]
            - datetime.timedelta(days=have_record_max_continus_day - 1)
            if nohave_record_max_continus_end_index
            else nohave_record_max_continus_max_day
        )
        self.save_data["nohave_record_max_continus"] = [
            nohave_record_max_continus_min_day,
            nohave_record_max_continus_max_day,
            nohave_record_max_continus_day,
        ]

        for call in self.raw_data["call_list"]:
            if (
                have_record_max_continus_min_day
                <= call["call_date"]
                <= have_record_max_continus_max_day
            ):
                phone = call["phone"]
                self.save_data["max_continus"][0] += 1
                self.save_data["max_continus"][1].add(phone)
                if call["duration_flag"] == "1":
                    self.save_data["max_continus"][2] += 1
                    self.save_data["max_continus"][3].add(phone)

    def cal_feature_sub_model_2(self):
        res_map = {}
        all_call_cnt = self.save_data["duration_all_name_all_cnt"]
        all_phone_cnt = len(self.save_data["duration_all_name_all_phone_set"])
        name_prefix = "have_record_max_continus"
        res_map["nohave_record_max_continus_days"] = self.save_data[
            "nohave_record_max_continus"
        ][2]
        res_map[name_prefix + "_days"] = self.save_data["have_record_max_continus"][2]
        res_map["have_record_days"] = self.save_data["have_record_days_cnt"]
        res_map[name_prefix + "_days_ratio"] = self.two_num_division(
            res_map["have_record_max_continus_days"], res_map["have_record_days"]
        )
        res_map[name_prefix + "_call_cnt"] = self.save_data["max_continus"][0]
        res_map[name_prefix + "_call_ratio"] = self.two_num_division(
            res_map[name_prefix + "_call_cnt"], all_call_cnt
        )
        res_map[name_prefix + "_phone_cnt"] = len(self.save_data["max_continus"][1])
        res_map[name_prefix + "_phone_ratio"] = self.two_num_division(
            res_map[name_prefix + "_phone_cnt"], all_phone_cnt
        )
        res_map[name_prefix + "_duration_1_call_cnt"] = self.save_data["max_continus"][
            2
        ]
        res_map[name_prefix + "_duration_1_call_ratio"] = self.two_num_division(
            res_map[name_prefix + "_duration_1_call_cnt"], all_call_cnt
        )
        res_map[name_prefix + "_duration_1_phone_cnt"] = len(
            self.save_data["max_continus"][3]
        )
        res_map[name_prefix + "_duration_1_phone_ratio"] = self.two_num_division(
            res_map[name_prefix + "_duration_1_phone_cnt"], all_phone_cnt
        )
        res_map[name_prefix + "_apply_start_days_diif"] = self.get_diff_day(
            self.raw_data["apply_time"], self.save_data["have_record_max_continus"][0]
        )
        res_map[name_prefix + "_apply_end_days_diif"] = self.get_diff_day(
            self.raw_data["apply_time"], self.save_data["have_record_max_continus"][1]
        )
        return res_map

    ###该模块主要为一些时间差特征
    def init_save_data_sub_model_3(self):
        pass

    def cal_save_data_sub_model_3(self):
        (
            self.save_data["del_nullmax(30)_min_date"],
            self.save_data["del_nullmax(30)_max_date"],
        ) = (
            self.save_data["nohave_record_max_continus"][1] + datetime.timedelta(days=1)
            if self.save_data["nohave_record_max_continus"][2] > 30
            else self.save_data["min_date"]
        ), self.save_data[
            "max_date"
        ]

    def cal_feature_sub_model_3(self):
        res_map = {}
        for name_prefix in ["", "del_nullmax(30)_"]:
            res_map[name_prefix + "start_date"] = str(
                self.save_data[name_prefix + "min_date"]
            )
            res_map[name_prefix + "end_date"] = str(
                self.save_data[name_prefix + "max_date"]
            )
            res_map[name_prefix + "apply_start_days_diff"] = self.get_diff_day(
                self.raw_data["apply_time"], self.save_data[name_prefix + "min_date"]
            )
            res_map[name_prefix + "apply_end_days_diff"] = self.get_diff_day(
                self.raw_data["apply_time"], self.save_data[name_prefix + "max_date"]
            )
            res_map[name_prefix + "end_start_days_diff"] = self.get_diff_day(
                self.save_data[name_prefix + "max_date"],
                self.save_data[name_prefix + "min_date"],
            )
        return res_map

    ###该模块主要为一些联系密度特征
    def init_save_data_sub_model_4(self):
        self.save_data["day_diff_list"] = [[] for i in range(6)]

    def cal_save_data_sub_model_4(self):
        for call in self.raw_data["call_list"]:
            diff_day = call["diff_days"]
            self.save_data["day_diff_list"][0].append(diff_day)
            (
                self.save_data["day_diff_list"][1].append(diff_day)
                if call["duration_flag"] == "1"
                else 1
            )
            (
                self.save_data["day_diff_list"][2].append(diff_day)
                if call["type_flag"] == "1"
                else 1
            )
            (
                self.save_data["day_diff_list"][3].append(diff_day)
                if call["type_flag"] == "2"
                else 1
            )
            (
                self.save_data["day_diff_list"][4].append(diff_day)
                if call["type_flag"] == "1" and call["duration_flag"] == "1"
                else 1
            )
            (
                self.save_data["day_diff_list"][5].append(diff_day)
                if call["type_flag"] == "2" and call["duration_flag"] == "1"
                else 1
            )

    def cal_feature_sub_model_4(self):
        res_map = {}
        name_prefix_list = [
            "",
            "duration_1_",
            "type_1_",
            "type_2_",
            "duration_1_type_1_",
            "duration_1_type_2_",
        ]
        window_prefix_dic = {"_m3": 90, "_m6": 180}
        for i in range(len(name_prefix_list)):
            for j in window_prefix_dic:
                res_map[name_prefix_list[i] + "density" + j] = self.calculate_density(
                    window_prefix_dic[j], self.save_data["day_diff_list"][i]
                )
        return res_map

    ###一些help函数
    ###二数相除
    def two_num_division(self, num1, num2, default=8):
        if num2 == 0:
            return "None"
        return round(num1 / num2, default)

    ###字符串转date
    def trans_str_to_date(self, str_date):
        if len(str_date) > 10:
            str_date = str_date[:10]
        return datetime.datetime.strptime(str_date, "%Y-%m-%d").date()

    ####date转字符串
    def trans_date_to_str(self, date):
        return datetime.datetime.strptime(date)

    ###计算两个时间的差天数
    def get_diff_day(self, time1, time2):
        time1 = self.trans_str_to_date(time1) if type(time1) == str else time1
        time2 = self.trans_str_to_date(time1) if type(time1) == str else time2
        try:
            time1 = time1.date()
        except:
            pass
        try:
            time2 = time1.date()
        except:
            pass
        return (time1 - time2).days

    ###获取L中取值为target的最大连续
    def get_max_continus(self, L, target, default=None):
        end_index = default
        cur_len = 0
        max_len = 0
        for i in range(len(L)):
            if L[i] == target:
                cur_len += 1
                if cur_len >= max_len:
                    max_len = cur_len
                    end_index = i
            else:
                cur_len = 0
        return max_len, end_index

    ###联系密度计算函数
    def calculate_density(self, section, data_list):
        data_list.sort()
        filter_data = [x for x in data_list if x <= section]
        if len(filter_data) == 0:
            return "None"
        result = pow(filter_data[0], 2)
        for i in range(1, len(filter_data)):
            result += pow((filter_data[i] - filter_data[i - 1]), 2)
        density = pow(result, 0.5)
        return density
